/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide;
import java.awt.event.*;
import java.text.MessageFormat;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
/** Implements a basic "wizard" GUI system.
* A list of <em>wizard panels</em> may be specified and these
* may be traversed at the proper times using the "Previous"
* and "Next" buttons (or "Finish" on the last one).
*
* @author Ian Formanek, Jaroslav Tulach
*/
public class WizardDescriptor extends DialogDescriptor {
/** "Next" button option.
* @see #setOptions */
public static final Object NEXT_OPTION = new Object ();
/** "Finish" button option.
* @see #setOptions */
public static final Object FINISH_OPTION = OK_OPTION;
/** "Previous" button option.
* @see #setOptions */
public static final Object PREVIOUS_OPTION = new Object ();
/** real buttons to be placed instead of the options */
private final JButton nextButton = new JButton ();
private final JButton finishButton = new JButton ();
private final JButton cancelButton = new JButton ();
private final JButton previousButton = new JButton ();
private static final ActionListener CLOSE_PREVENTER = new ActionListener () {
public void actionPerformed (ActionEvent evt) {
}
};
{
// button init
ResourceBundle b = NbBundle.getBundle ("org.openide.Bundle"); // NOI18N
nextButton.setText (b.getString ("CTL_NEXT"));
previousButton.setText (b.getString ("CTL_PREVIOUS"));
finishButton.setText (b.getString ("CTL_FINISH"));
cancelButton.setText (b.getString ("CTL_CANCEL"));
finishButton.setDefaultCapable (true);
nextButton.setDefaultCapable (true);
previousButton.setDefaultCapable (false);
cancelButton.setDefaultCapable (false);
}
/** Iterator between panels in the wizard */
private Iterator panels;
/** Change listener that invokes method update state */
private Listener listener;
/** current panel */
private Panel current;
/** settings to be used for the panels */
private Object settings;
/** message format to create title of the document */
private MessageFormat titleFormat;
/** hashtable with additional settings that is usually used
* by Panels to store their data
* @associates Object
*/
private Map properties;
/** Create a new wizard from a fixed list of panels, passing some settings to the panels.
* @param wizardPanels the panels to use
* @param settings the settings to pass to panels, or <code>null</code>
* @see #WizardDescriptor(WizardDescriptor.Iterator, Object)
*/
public WizardDescriptor (Panel[] wizardPanels, Object settings) {
this (new ArrayIterator (wizardPanels), settings);
}
/** Create a new wizard from a fixed list of panels with settings
* defaulted to <CODE>this</CODE>.
*
* @param wizardPanels the panels to use
* @see #WizardDescriptor(WizardDescriptor.Iterator, Object)
*/
public WizardDescriptor (Panel[] wizardPanels) {
// passing CLOSE_PREVENTER which is treated especially
this (wizardPanels, CLOSE_PREVENTER);
}
/** Create wizard for a sequence of panels, passing some settings to the panels.
* @param panels iterator over all {@link WizardDescriptor.Panel}s that can appear in the wizard
* @param settings the settings to provide to the panels (may be any data understood by them)
* @see WizardDescriptor.Panel#readSettings
* @see WizardDescriptor.Panel#storeSettings
*/
public WizardDescriptor (Iterator panels, Object settings) {
super ("", "", true, DEFAULT_OPTION, null, CLOSE_PREVENTER); // NOI18N
this.settings = settings == CLOSE_PREVENTER ? this : settings;
listener = new Listener ();
nextButton.addActionListener (listener);
previousButton.addActionListener (listener);
finishButton.addActionListener (listener);
cancelButton.addActionListener (listener);
super.setOptions (new Object[] { previousButton, nextButton, finishButton, cancelButton });
super.setClosingOptions (new Object[] { finishButton, cancelButton });
setPanels (panels);
}
/** Create wizard for a sequence of panels, with settings
* defaulted to <CODE>this</CODE>.
*
* @param panels iterator over all {@link WizardDescriptor.Panel}s that can appear in the wizard
*/
public WizardDescriptor (Iterator panels) {
// passing CLOSE_PREVENTER which is treated especially
this (panels, CLOSE_PREVENTER);
}
/** Set a different list of panels.
* Correctly updates the buttons.
* @param panels the new list of {@link WizardDescriptor.Panel}s
*/
public final synchronized void setPanels (Iterator panels) {
if (panels != null) {
panels.removeChangeListener (listener);
}
this.panels = panels;
panels.addChangeListener (listener);
updateState ();
}
/** Set options permitted by the wizard considered as a <code>DialogDescriptor</code>.
* Substitutes tokens such as {@link #NEXT_OPTION} with the actual button.
*
* @param options the options to set
*/
public void setOptions (Object[] options) {
super.setOptions (convertOptions (options));
}
/**
* @param options the options to set
*/
public void setAdditionalOptions (Object[] options) {
super.setAdditionalOptions (convertOptions (options));
}
/**
* @param options the options to set
*/
public void setClosingOptions (Object[] options) {
super.setClosingOptions (convertOptions (options));
}
/** Converts some options.
*/
private Object[] convertOptions (Object[] options) {
options = (Object[])options.clone ();
for (int i = options.length - 1; i >= 0; i--) {
if (options[i] == NEXT_OPTION) options[i] = nextButton;
if (options[i] == PREVIOUS_OPTION) options[i] = previousButton;
if (options[i] == FINISH_OPTION) options[i] = finishButton;
if (options[i] == CANCEL_OPTION) options[i] = cancelButton;
}
return options;
}
/** Sets the message format to create title of the wizard.
* The format can take two parameters. The name of the
* current component and the name returned by the iterator that
* defines the order of panels. The default value is something
* like
* <PRE>
* {0} wizard {1}
* </PRE>
* That can be expanded to something like this
* <PRE>
* EJB wizard (1 of 8)
* </PRE>
* This method allows anybody to provide own title format.
*
* @param format message format to the title
*/
public void setTitleFormat (MessageFormat format) {
titleFormat = format;
updateState ();
}
/** Getter for current format to be used to format title.
* @return the format
* @see #setTitleFormat
*/
public MessageFormat getTitleFormat () {
if (titleFormat == null) {
synchronized (this) {
if (titleFormat == null) {
// ok, initialize the default one
titleFormat = new MessageFormat (NbBundle.getBundle (
WizardDescriptor.class
).getString ("CTL_WizardName"));
}
}
}
return titleFormat;
}
/** Allows Panels that use WizardDescriptor as settings object to
* store additional settings into it.
*
* @param name name of the property
* @param value value of property
*/
public synchronized void putProperty (String name, Object value) {
if (properties == null) {
properties = new HashMap (7);
}
properties.put (name, value);
}
/** Getter for stored property.
* @param name name of the property
* @return the value
*/
public synchronized Object getProperty (String name) {
return properties == null ? null : properties.get (name);
}
/** Updates buttons to reflect the current state of the panels.
* Can be overridden by subclasses
* to change the options to special values. In such a case use:
* <p><code><PRE>
* super.updateState ();
* setOptions (...);
* </PRE></code>
*/
protected synchronized void updateState () {
boolean next = panels.hasNext ();
boolean prev = panels.hasPrevious ();
Panel p = panels.current ();
// listeners on the panel
if (current != p) {
if (current != null) {
// remove
current.removeChangeListener (listener);
current.storeSettings (settings);
}
// add to new
p.addChangeListener (listener);
current = p;
current.readSettings (settings);
}
boolean valid = p.isValid ();
nextButton.setEnabled (next && valid);
previousButton.setEnabled (prev);
finishButton.setEnabled (
valid &&
(!next || (current instanceof FinishPanel))
);
// nextButton.setVisible (next);
// finishButton.setVisible (!next || (current instanceof FinishPanel));
if (next) {
setValue (nextButton);
} else {
setValue (finishButton);
}
setHelpCtx (p.getHelp ());
java.awt.Component c = p.getComponent ();
String panelName = c.getName ();
if (panelName == null) {
panelName = ""; // NOI18N
}
if (c != getMessage ()) {
setMessage (c);
}
Object[] args = {
panelName,
panels.name ()
};
MessageFormat mf = getTitleFormat ();
setTitle (mf.format (args));
}
/** Iterator on the sequence of panels.
* @see WizardDescriptor.Panel
*/
public interface Iterator {
/** Get the current panel.
* @return the panel
*/
public Panel current ();
/** Get the name of the current panel.
* @return the name
*/
public String name ();
/** Test whether there is a next panel.
* @return <code>true</code> if so
*/
public boolean hasNext ();
/** Test whether there is a previous panel.
* @return <code>true</code> if so
*/
public boolean hasPrevious ();
/** Move to the next panel.
* I.e. increment its index, need not actually change any GUI itself.
* @exception NoSuchElementException if the panel does not exist
*/
public void nextPanel ();
/** Move to the previous panel.
* I.e. decrement its index, need not actually change any GUI itself.
* @exception NoSuchElementException if the panel does not exist
*/
public void previousPanel ();
/** Add a listener to changes of the current panel.
* The listener is notified when the possibility to move forward/backward changes.
* @param l the listener to add
*/
public void addChangeListener (ChangeListener l);
/** Remove a listener to changes of the current panel.
* @param l the listener to remove
*/
public void removeChangeListener (ChangeListener l);
}
/** One wizard panel with a component on it. */
public interface Panel {
/** Get the component displayed in this panel.
* @return the component
*/
public java.awt.Component getComponent ();
/** Help for this panel.
* When the panel is active, this is used as the help for the wizard dialog.
* @return the help or <code>null</code> if no help is supplied
*/
public HelpCtx getHelp ();
/** Provides the wizard panel with the current data--either
* the default data or already-modified settings, if the user used the previous and/or next buttons.
* This method can be called multiple times on one instance of <code>WizardDescriptor.Panel</code>.
* @param settings the object representing wizard panel state, as originally supplied to {@link WizardDescriptor#WizardDescriptor(WizardDescriptor.Iterator,Object)}
*/
public void readSettings (Object settings);
/** Provides the wizard panel with the opportunity to update the
* settings with its current customized state.
* Rather than updating its settings with every change in the GUI, it should collect them,
* and then only save them when requested to by this method.
* Also, the original settings passed to {@link #readSettings} should not be modified (mutated);
* rather, the (copy) passed in here should be mutated according to the collected changes.
* This method can be called multiple times on one instance of <code>WizardDescriptor.Panel</code>.
* @param settings the object representing a settings of the wizard
*/
public void storeSettings (Object settings);
/** Test whether the panel is finished and it is safe to proceed to the next one.
* If the panel is valid, the "Next" (or "Finish") button will be enabled.
* @return <code>true</code> if the user has entered satisfactory information
*/
public boolean isValid ();
/** Add a listener to changes of the panel's validity.
* @param l the listener to add
* @see #isValid
*/
public void addChangeListener (ChangeListener l);
/** Remove a listener to changes of the panel's validity.
* @param l the listener to remove
*/
public void removeChangeListener (ChangeListener l);
}
/** A special interface for panels in middle of the
* iterators path that would like to have the finish button
* enabled. So both Next and Finish are enabled on panel
* implementing this interface.
*/
public interface FinishPanel extends Panel {
}
/** Special iterator that works on an array of <code>Panel</code>s.
*/
public static class ArrayIterator extends Object implements Iterator {
/** Array of items.
*/
private Panel[] panels;
/** Index into the array
*/
private int index;
/** Construct an iterator.
* @param array the list of panels to use
*/
public ArrayIterator (Panel[] array) {
panels = array;
index = 0;
}
/* The current panel.
*/
public Panel current () {
return panels[index];
}
/* Current name of the panel */
public String name () {
Object[] args = {
new Integer (index + 1),
new Integer (panels.length)
};
MessageFormat mf = new MessageFormat (NbBundle.getBundle (WizardDescriptor.class).getString ("CTL_ArrayIteratorName"));
return mf.format (args);
}
/* Is there a next panel?
* @return true if so
*/
public boolean hasNext () {
return index < panels.length - 1;
}
/* Is there a previous panel?
* @return true if so
*/
public boolean hasPrevious () {
return index > 0;
}
/* Moves to the next panel.
* @exception NoSuchElementException if the panel does not exist
*/
public synchronized void nextPanel () {
if (index + 1 == panels.length) throw new java.util.NoSuchElementException ();
index++;
}
/* Moves to previous panel.
* @exception NoSuchElementException if the panel does not exist
*/
public synchronized void previousPanel () {
if (index == 0) throw new java.util.NoSuchElementException ();
index--;
}
/* Ignores the listener, there are no changes in order of panels.
*/
public void addChangeListener (ChangeListener l) {
}
/* Ignored.
*/
public void removeChangeListener (ChangeListener l) {
}
}
/** Listener to changes in the iterator and panels.
*/
private final class Listener extends Object
implements ChangeListener, ActionListener {
/** Change in the observed objects */
public void stateChanged (ChangeEvent ev) {
updateState ();
}
/** Action listener */
public void actionPerformed (ActionEvent ev) {
if (ev.getSource () == nextButton) {
panels.nextPanel ();
updateState ();
}
if (ev.getSource () == previousButton) {
panels.previousPanel ();
updateState ();
}
if (ev.getSource () == finishButton) {
current.storeSettings (settings);
setValue (OK_OPTION);
}
if (ev.getSource () == cancelButton) {
current.storeSettings (settings);
setValue (CANCEL_OPTION);
}
}
}
}
/*
* Log
* 19 src-jtulach1.18 1/13/00 Ian Formanek NOI18N
* 18 src-jtulach1.17 1/8/00 Jaroslav Tulach
* 17 src-jtulach1.16 1/8/00 Jaroslav Tulach
* 16 src-jtulach1.15 12/15/99 Jesse Glick There is a now a Help
* button automatically added to any dialog (though not notification
* dialogs) which provides help either explicitly or on its inner
* component.
* 15 src-jtulach1.14 12/9/99 Jaroslav Tulach Can be used more than
* once.
* 14 src-jtulach1.13 11/25/99 Jaroslav Tulach Both Next & Finish are
* always visible.
* 13 src-jtulach1.12 11/24/99 Jaroslav Tulach New "New From Template"
* Dialog
* 12 src-jtulach1.11 11/4/99 Jaroslav Tulach DialogDescriptor.setClosingOptions
*
* 11 src-jtulach1.10 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 10 src-jtulach1.9 8/10/99 Martin Ryzl isValid and readSettings
* order in updateState changed
* 9 src-jtulach1.8 6/24/99 Jesse Glick Gosh-honest HelpID's.
* 8 src-jtulach1.7 6/11/99 Slavek Psenicka storeSettings was not
* called at last pane
* 7 src-jtulach1.6 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 6 src-jtulach1.5 6/8/99 Ian Formanek oved here from package
* awt
* 5 src-jtulach1.4 6/8/99 Ian Formanek Close on button press
* prevented
* 4 src-jtulach1.3 6/4/99 Jaroslav Tulach Improved NbDialog.
* 3 src-jtulach1.2 6/3/99 Ian Formanek
* 2 src-jtulach1.1 5/15/99 Jesse Glick [JavaDoc]
* 1 src-jtulach1.0 3/20/99 Jaroslav Tulach
* $
*/